#include "Utils.h"
#include "Lang.h"

inline void ind(int indent) { for(int i = 0; i < indent; i++) printf("\t"); } // ind

void dump(YAML::Node n, int indent) {
    switch(n.Type()) {
    case YAML::NodeType::Map:
        printf(" (map[%lu]):\n", n.size()); 
        for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
            YAML::Node o = (*i).first;
            ind(indent); printf(" %s", o.Scalar().c_str());
            dump((*i).second, indent+1);
        }
        break;
    case YAML::NodeType::Null: printf(" Null\n"); break;
    case YAML::NodeType::Scalar:
        printf(" (scalar): \"%s\"\n", n.Scalar().c_str());
        break;
    case YAML::NodeType::Sequence:
        printf(" (sequence[%lu]):\n", n.size());
        for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
            YAML::Node o = (*i);
            ind(indent+1); dump(o, indent+2);
        }
        break;
    case YAML::NodeType::Undefined: printf("(Undefined)\n"); break;
    default: printf("(Unknown)(%d)\n", n.Type());
    }
} // dump

YAML::Node getNode(YAML::Node parent, std::string path) {
    std::string::size_type p;
    std::string n;
    SYVDEBUGOUT(SYVTRACE,
    printf("getNode(parent, \"%s\"); dump(parent):\n", path.c_str());
    dump(parent);
    )
    if(!path.length()) { return parent; }
    if(!parent.IsMap()) return YAML::Node(YAML::NodeType::Undefined);
    do {
        p = path.find(SYV_VARIABLE_PATH_SEPARATOR);
        if(p != std::string::npos) {
            n += path.substr(0, p);
            path = path.substr(p + 1, path.length() - p - 1);
        } else {
            n += path;
            path = "";
        }
        if(!parent[n]) {
            if(!path.length()) {
                    return YAML::Node(YAML::NodeType::Undefined);
            }
            n += SYV_VARIABLE_PATH_SEPARATOR;
        } else {
            std::string::size_type q = path.find(SYV_VARIABLE_PATH_SEPARATOR);
            if(q == std::string::npos) {
                if(!path.length()) return parent[n];
            }
            return getNode(parent[n], path);
        }
    } while(1);
} // getNode

YAML::Node overrideValues(YAML::Node dflt, YAML::Node overridden) {

    if(!dflt) return overridden;
    if(!overridden) return dflt;

//    printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
//    printf("dflt:\n");
//    dump(dflt);
//    printf("overridden:\n");
//    dump(overridden);



    YAML::Node combined;

    if(dflt.IsMap()) {
        for(YAML::Node::const_iterator i = dflt.begin(); i != dflt.end(); i++) {
            if(overridden[i->first.Scalar()])
                combined[i->first.Scalar()] = overrideValues(dflt[i->first.Scalar()], overridden[i->first.Scalar()]);
            else
                combined[i->first.Scalar()] = dflt[i->first.Scalar()];
        }
        for(YAML::Node::const_iterator i = overridden.begin(); i != overridden.end(); i++) {
            if(!combined[i->first.Scalar()]) {
                combined[i->first.Scalar()] = overridden[i->first.Scalar()];
            }
        }
    } else if(dflt.IsSequence()) {
        for(unsigned int i = 0; i < dflt.size(); i++) {
            if(overridden[i])
                combined[i] = overrideValues(dflt[i], overridden[i]);
            else
                combined[i] = dflt[i];
        }
        if(overridden.size() > dflt.size()) {
            for(unsigned int i = dflt.size(); i < overridden.size(); i++) {
                combined[i] = overridden[i];
            }
        }
    } else {
        if(overridden && !overridden.IsNull())
            combined = overridden;
        else
            combined = dflt;
    }

//    printf("combined:\n");
//    dump(combined);
//    printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
    return combined;
}

bool structureContainsNull(YAML::Node structure) {

    if(structure.IsScalar()) {
        return structure.IsNull();
    } else if(structure.IsMap()) {
        for(YAML::Node::const_iterator i = structure.begin(); i != structure.end(); i++) {
            if(structureContainsNull(i->second)) return true;
        }
    } else if(structure.IsSequence()) {
        for(YAML::Node::const_iterator i = structure.begin(); i != structure.end(); i++) {
            if(structureContainsNull(*i)) return true;
        }
    }

    return false;
}


void dump(sv x) {
    printf("Dumping...\n");
    for(sv::const_iterator i = x.begin(); i!= x.end(); i++){
        printf("%s\n", i->c_str());
    }
    printf("End of dump.\n");
}


bool containsScalarValue(YAML::Node n, std::string v) {

    if(n.IsScalar()) {
        if(!n.Scalar().compare(v)) return true;
    } else if(n.IsMap()) {
        for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
            if(containsScalarValue(i->second, v)) return true;
        }
    } else if(n.IsSequence()) {
        for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
            if(containsScalarValue(*i, v)) return true;
        }
    }
    
    return false;
}

bool mapHasKeyWithValue(YAML::Node map, std::string value) {
    for(YAML::Node::const_iterator i = map.begin(); i != map.end(); i++) {
        if(!i->second.Scalar().compare(value)) return true;
    }
    return false;
}

bool sequenceHasValue(YAML::Node seq, std::string value) {
    for(YAML::Node::const_iterator i = seq.begin(); i != seq.end(); i++) {
        if(!i->Scalar().compare(value)) return true;
    }
    return false;
}

YAML::Node copy(YAML::Node n) {
    switch(n.Type()) {
    case YAML::NodeType::Map: {
        YAML::Node r(YAML::NodeType::Map);
        for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
            r[i->first.Scalar()] = copy(i->second);
        }
        return r;
    }
    case YAML::NodeType::Scalar: {
        YAML::Node r;
        r = n.Scalar();
        return r;
    }
    case YAML::NodeType::Sequence: {
        YAML::Node r(YAML::NodeType::Sequence);
        for(YAML::Node::const_iterator i = n.begin(); i != n.end(); i++) {
            r.push_back(copy(*i));
        }
        return r;
    }
    case YAML::NodeType::Null: {
        return YAML::Node(YAML::NodeType::Null);
    }
    case YAML::NodeType::Undefined: {
        return YAML::Node(YAML::NodeType::Undefined);
    }
    default: {
        return YAML::Node(n.Type());
    }
    }
}

std::string shorthand(YAML::Node n) {
    std::string s;
    switch(n.Type()) {
    case YAML::NodeType::Map: {
        s += " {";
        
        for(YAML::Node::const_iterator i = n.begin(); i != n.end();) {
            s += " ";
            s += i->first.Scalar();
            s += ":";
            s += shorthand(i->second);
            i++;
            if(i != n.end())
                s += ",";
        }
        s += " }";
    }
        break;
    case YAML::NodeType::Null: {
        s += " (null)"; break;
    }
    case YAML::NodeType::Scalar: {
        s += " \"" + n.Scalar() + "\"";
    }
        break;
    case YAML::NodeType::Sequence: {
        s += " [";
        for(YAML::Node::const_iterator i = n.begin(); i != n.end();) {
            s += shorthand(*i);
            i++;
            if(i != n.end())
                s += ",";
        }
        s += " ]";
    }
        break;
    case YAML::NodeType::Undefined: {
        s += "(Undefined)";
    }
    break;
    default: {
        s += "(Unknown(";
        s += n.Type();
        s += "))";
    }
    }
    return s;
}

bool comparesEqual(YAML::Node a, YAML::Node b) {
    if(a.IsScalar()) {
        if(!a.Scalar().compare(b.Scalar())) return true;
    } else if(a.IsMap()) {
        for(YAML::Node::const_iterator i = a.begin(); i != a.end(); i++) {
            if(!comparesEqual(i->second, b[i->first.Scalar()])) {
                dump(i->second);
                printf("Not equal to:\n");
                dump(b[i->first.Scalar()]);
                return false;
            }
        }
        for(YAML::Node::const_iterator i = b.begin(); i != b.end(); i++) {
            if(!comparesEqual(i->second, a[i->first.Scalar()])) return false;
        }
        return true;
    } else if(a.IsSequence()) {
        for(unsigned int i = 0; i < a.size(); i++)
            if(!comparesEqual(a[i], b[i])) return false;
        return true;
    } else if(a.IsNull()) {
        return b.IsNull();
    }
}
